#pragma once
#include <cstdint>
#include <string>
#include <sys/types.h>
namespace xncc {
namespace types {
    class DateTime
    {
      public:
        /// Magic numbers
        static constexpr int64_t SECONDS_PER_DAY  = 86400;
        static constexpr int64_t SECONDS_PER_HOUR = 3600;
        static constexpr int64_t SECONDS_PER_MIN  = 60;
        static constexpr int64_t MINUTES_PER_HOUR = 60;
        static constexpr int64_t NANOS_PER_DAY    = 86400000000000LL;
        static constexpr int64_t NANOS_PER_HOUR   = 3600000000000LL;
        static constexpr int64_t NANOS_PER_MIN    = 60000000000LL;
        static constexpr int32_t NANOS_PER_SEC    = 1000000000;
        static constexpr int32_t MICROS_PER_SEC   = 1000000;
        static constexpr int32_t MILLS_PER_SEC    = 1000;
        static constexpr int32_t MICROS_PER_MILL  = 1000;

        static constexpr int YEAR_1900 = 1900;

        enum TsType : uint8_t
        {
            TS_TYPE_SECOND = 0,
            TS_TYPE_MILLI  = 1,
            TS_TYPE_MICRO  = 2,
            TS_TYPE_NANO   = 3,
        };

        DateTime(int date, int64_t time);
        DateTime(int year, int month, int day, int hour, int minute, int second, int millis);
        DateTime(int year, int month, int day, int hour, int minute, int second, int fraction, int precision);

        static DateTime nowUtc();

        static DateTime    nowLocal();
        static std::string readableTime(time_t t);

        static int64_t timeMicro();
        static int64_t timeMilli() { return timeMicro() / MILLS_PER_SEC; }
        static int64_t timeNano();
        /*
            format 2021-09-23_18:00:00.123
        */
        [[nodiscard]] std::string toString() const;
        [[nodiscard]] std::string toStringNoFraction() const;
        [[nodiscard]] std::string toStringWithMillSecond() const;
        [[nodiscard]] std::string toStringWithMicroSecond() const;
        [[nodiscard]] std::string toStringWithNanoSecond() const;

        [[nodiscard]] int getYear() const
        {
            int y = 0;
            int m = 0;
            int d = 0;
            getYMD(y, m, d);
            return y;
        }
        [[nodiscard]] int getMonth() const
        {
            int y = 0;
            int m = 0;
            int d = 0;
            getYMD(y, m, d);
            return m;
        }
        [[nodiscard]] int getDay() const
        {
            int y = 0;
            int m = 0;
            int d = 0;
            getYMD(y, m, d);
            return d;
        }
        void getYMD(int& year, int& month, int& day) const;
        void getHMS(int& hour, int& minute, int& second, int& millis) const;
        void getHMS(int& hour, int& minute, int& second, int& fraction, int precision) const;

        /// (0-23)
        [[nodiscard]] int getHour() const { return static_cast<int>(timeNum / NANOS_PER_HOUR); }

        /// (0-59)
        [[nodiscard]] int getMinute() const { return static_cast<int>((timeNum / NANOS_PER_MIN) % MINUTES_PER_HOUR); }

        /// (0-59)
        [[nodiscard]] int getSecond() const { return static_cast<int>((timeNum / NANOS_PER_SEC) % SECONDS_PER_MIN); }

        /// (0-999)
        [[nodiscard]] int getMillisecond() const { return (getNanosecond() / PRECISION_FACTOR[3]); }

        /// Return the microsecond portion of the time
        [[nodiscard]] int getMicrosecond() const { return (getNanosecond() / PRECISION_FACTOR[6]); }

        /// Return the nanosecond portion of the time
        [[nodiscard]] int getNanosecond() const { return static_cast<int>((timeNum) % NANOS_PER_SEC); }

        /// Calculate the weekday of the date (Sunday is 0, Saturday is 6)
        [[nodiscard]] int getWeekDay() const
        {
            int Y = 0, M = 0, D = 0;
            getYMD(Y, M, D);
            int m      = M >= 3 ? M - 2 : M + 10;
            int Yprime = M >= 3 ? Y : Y - 1;
            int y      = Yprime % 100;
            int c      = Yprime / 100;
            int wd     = (D + int(2.6 * m - 0.2) + y + int(y / 4) + int(c / 4) - (2 * c)) % 7;
            return (wd < 0 ? 7 + wd : wd);
        }

        void operator+=(int seconds)
        {
            int d = seconds / SECONDS_PER_DAY;
            int s = seconds % SECONDS_PER_DAY;

            julianDateNum += d;
            timeNum += s * NANOS_PER_SEC;

            if (timeNum > NANOS_PER_DAY) {
                julianDateNum++;
                timeNum %= NANOS_PER_DAY;
            }
            else if (timeNum < 0) {
                julianDateNum--;
                timeNum += NANOS_PER_DAY;
            }
        }
        bool operator==(const DateTime& rhs)
        {
            return this->julianDateNum == rhs.julianDateNum && this->timeNum == rhs.timeNum;
        }

        bool operator!=(const DateTime& rhs) { return !(*this == rhs); }

        bool operator<(const DateTime& rhs)
        {
            if (this->julianDateNum < rhs.julianDateNum)
                return true;
            else if (this->julianDateNum > rhs.julianDateNum)
                return false;
            else if (this->timeNum < rhs.timeNum)
                return true;
            return false;
        }

        bool operator>(const DateTime& rhs) { return !(*this == rhs || *this < rhs); }

        bool operator<=(const DateTime& rhs) { return *this == rhs || *this < rhs; }

        bool operator>=(const DateTime& rhs) { return *this == rhs || *this > rhs; }

        // 返回值是秒
        int operator-(const DateTime& rhs)
        {
            return static_cast<int>((DateTime::SECONDS_PER_DAY * (this->julianDateNum - rhs.julianDateNum) +
                                     // Truncate the nanos before subtracting
                                     static_cast<int>(this->timeNum / DateTime::NANOS_PER_SEC) -
                                     static_cast<int>(rhs.timeNum / DateTime::NANOS_PER_SEC)));
        }

      protected:
        static DateTime   fromTm(const tm& tm, int fraction, int precision);
        static DateTime   fromTm(const tm& tm, int millis = 0);
        static int        julianDate(int year, int month, int day);
        static int64_t    makeHMS(int hour, int minute, int second, int nanos);
        static int        convertToNanos(int fraction, int precision);
        [[nodiscard]] int getFraction(int precision) const;
        std::string       toStringPrecision(int precision, const char* fmt) const;

        int     julianDateNum;  // 儒略日数
        int64_t timeNum;
        // time_t epoch (1970-01-01) as a Julian date
        static constexpr int64_t JULIAN_19700101 = 2440588;

      private:
        static constexpr int PRECISION_FACTOR[10] = {
            NANOS_PER_SEC, 100000000, 10000000, MICROS_PER_SEC, 100000, 10000, MILLS_PER_SEC, 100, 10, 1};
    };
    class LocalTimeStamp;
    class UtcTimeStamp : public DateTime
    {
      public:
        UtcTimeStamp() : DateTime(DateTime::nowUtc()) {}

        explicit UtcTimeStamp(int64_t ts, TsType type = TsType::TS_TYPE_MICRO)
            : DateTime(
                  type == TS_TYPE_SECOND ? fromUtcTimeT(ts, 0) :
                  type == TS_TYPE_MILLI  ? fromUtcTimeT(ts / MILLS_PER_SEC, static_cast<int>(ts % MILLS_PER_SEC), 3) :
                  type == TS_TYPE_MICRO  ? fromUtcTimeT(ts / MICROS_PER_SEC, static_cast<int>(ts % MICROS_PER_SEC), 6) :
                                           fromUtcTimeT(ts / NANOS_PER_SEC, static_cast<int>(ts % NANOS_PER_SEC), 9))
        {
        }

        UtcTimeStamp(time_t time, int fraction, int precision) : DateTime(fromUtcTimeT(time, fraction, precision)) {}

        explicit UtcTimeStamp(const tm* time, int millisecond = 0) : DateTime(fromTm(*time, millisecond)) {}

        UtcTimeStamp(const tm* time, int fraction, int precision) : DateTime(fromTm(*time, fraction, precision)) {}
        LocalTimeStamp toLocal();

        /*
            format 2021-09-23_18:00:00.123
        */
        static int convert(const std::string& value, UtcTimeStamp& utc_time);

      private:
        static DateTime fromUtcTimeT(time_t t, int millis = 0);
        static DateTime fromUtcTimeT(time_t t, int fraction, int precision);
    };

    class LocalTimeStamp : public DateTime
    {
      public:
        LocalTimeStamp() : DateTime(DateTime::nowLocal()) {}
        explicit LocalTimeStamp(int64_t ts, TsType type = TsType::TS_TYPE_MICRO)
            : DateTime(type == TS_TYPE_SECOND ?
                           fromLocalTimeT(ts, 0) :
                       type == TS_TYPE_MILLI ?
                           fromLocalTimeT(ts / MILLS_PER_SEC, static_cast<int>(ts % MILLS_PER_SEC), 3) :
                       type == TS_TYPE_MICRO ?
                           fromLocalTimeT(ts / MICROS_PER_SEC, static_cast<int>(ts % MICROS_PER_SEC), 6) :
                           fromLocalTimeT(ts / NANOS_PER_SEC, static_cast<int>(ts % NANOS_PER_SEC), 9))
        {
        }

        LocalTimeStamp(time_t time, int fraction, int precision) : DateTime(fromLocalTimeT(time, fraction, precision))
        {
        }

        explicit LocalTimeStamp(const tm* time, int millisecond = 0) : DateTime(fromTm(*time, millisecond)) {}

        LocalTimeStamp(const tm* time, int fraction, int precision) : DateTime(fromTm(*time, fraction, precision)) {}
        UtcTimeStamp toUtc();

        /*
            format 2021-09-23_18:00:00.123
        */
        static int convert(const std::string& value, LocalTimeStamp& local_time);

      private:
        static DateTime fromLocalTimeT(time_t t, int millis = 0);
        static DateTime fromLocalTimeT(time_t t, int fraction, int precision);
    };

}  // namespace types
}  // namespace xncc
